﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the Microsoft Public License (Ms-PL).
// See accompanying file License.txt or copy at http://mews.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Mews.Mime
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;

    using Mews.IO;

    /// <summary>Represents an entity body (see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a> 2.4. &amp;
    /// 2.6.) with binary content (see <a href="http://www.ietf.org/rfc/rfc2046.txt">RFC 2046</a> 4.5.1.).</summary>
    /// <threadsafety static="true" instance="false"/>
    [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "MemoryStream does not need to be disposed.")]
    public sealed class ApplicationBody : Body<ApplicationBody>
    {
        private MemoryStream content = new MemoryStream();

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Initializes a new instance of the <see cref="ApplicationBody"/> class.</summary>
        /// <param name="subtype">The subtype of the application body, usually one of the
        /// <see cref="ApplicationSubtypeNames"/> members.</param>
        /// <exception cref="ArgumentException"><paramref name="subtype"/> is <c>null</c> or empty.</exception>
        public ApplicationBody(string subtype) : base(subtype)
        {
        }

        /// <summary>Gets a copy of the body content.</summary>
        /// <remarks>The default value is an empty array, which is set during construction and when the parser does not
        /// find a valid body.</remarks>
        public byte[] GetContent()
        {
            return this.content.ToArray();
        }

        /// <summary>Sets the body content.</summary>
        /// <param name="value">A Stream containing the new content.</param>
        /// <exception cref="ArgumentNullException"><paramref name="value"/> equals <c>null</c>.</exception>
        /// <remarks>
        /// <para>The passed <see cref="MemoryStream"/> must not be modified after calling this method.</para>
        /// <para>This method and <see cref="GetContent"/> are not coalesced into a property for performance reasons.
        /// Calling code is expected to not normally know the length of the content upfront. Instead, content is often
        /// assembled in a <see cref="MemoryStream"/>. Forcing the caller to convert it to an array of bytes before
        /// setting would thus incurr one unnecessary copy of the content.</para>
        /// </remarks>
        public void SetContent(MemoryStream value)
        {
            // TODO: This could probably be done more efficiently by accepting offset and count parameters.
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            this.content = value;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal ApplicationBody(FallbackReason fallbackReason) :
            base(ApplicationSubtypeNames.OctetStream, fallbackReason)
        {
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="Body.WriteTo"/>.</summary>
        protected internal sealed override void WriteTo(
            Stream stream, Action<IEnumerable<HeaderField>> writeHeaderFieldsCallback)
        {
            writeHeaderFieldsCallback(this.GetRequiredHeaderFields(
                MediaTypeNames.Application, new KeyValuePair<string, string>[0], ContentTransferEncodingNames.Base64));
            StreamHelper.WriteLineBreak(stream);

            using (var base64Stream = new Base64Stream(new ForwardingStream(stream)))
            {
                this.content.WriteTo(base64Stream);
            }
        }
    }
}
